package es.udc.acs.banco.operations;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Formatter;

import es.udc.acs.banco.exceptions.CuentaDestinoException;
import es.udc.acs.banco.exceptions.CuentaNoValidaException;
import es.udc.acs.banco.exceptions.CuentaOrigenDestinoIgualesException;
import es.udc.acs.banco.exceptions.CuentaOrigenException;
import es.udc.acs.banco.exceptions.CuentaOrigenSaldoInsuficienteException;
import es.udc.acs.banco.exceptions.CuentaSaldoInsuficienteException;
import es.udc.acs.banco.exceptions.TarjetaNoValidaException;
import es.udc.acs.database.Database;
import es.udc.acs.database.exception.ConexionInvalidaException;
import es.udc.acs.utils.CodigosFAP;

public class DBBancoOperations {

	private DBBancoOperations(){}
	
	/**
	 * Consulta el saldo de una cuenta
	 */
	public static String consultaSaldo(String idBanco, 
									   String idTarjeta, 
									   String cuentaTarjeta)
									   throws SQLException, 
									          CuentaNoValidaException, 
									          TarjetaNoValidaException, 
									          ConexionInvalidaException {
		
		String cbanco;
		cbanco = DBBancoOperations.
				comprobarCuenta(idBanco, idTarjeta, cuentaTarjeta);
		
	    String sentencia = 
	    "SELECT saldo "+
		"FROM cbanco "+
		"WHERE (id_banco = '"+idBanco+"')"+
		" AND (id_cbanco = "+cbanco+")";

	    Connection conexion = Database.getInstancia().getConexion();
		
		if (conexion != null) {
			
			Statement stmt   = conexion.createStatement();
	        ResultSet result = stmt.executeQuery(sentencia);	        
	        result.next();
	        
	        int saldo = result.getInt("saldo");         

	        stmt.close();
	        
	        return obtenerRespuestaSaldo(saldo);	        
		}
		
		throw new ConexionInvalidaException();
	}
	
	
	/**
	 * Crea el cuerpo necesario para la respuesta de la consulta 
	 * de saldo
	 */
	private static String obtenerRespuestaSaldo(int saldo) {
       
		char signo = '+';
        Formatter fmt = new Formatter();
		
        if (saldo < 0) signo = '-';	     
        
        // añadimos ceros a la izquierda
        fmt.format("%010d",saldo);
        
		return signo+fmt.toString();
	}
	
	
	/**
	 * Consulta de los 20 primeros movimientos de una cuenta
	 */
	public static String consultaMovimientos(String idBanco, 
			                                 String idTarjeta,
			                                 String cuentaTarjeta) 
			                                 throws SQLException, 
			                                        CuentaNoValidaException, 
			                                        TarjetaNoValidaException, 
			                                        ConexionInvalidaException {
		
		String cbanco;
		cbanco = DBBancoOperations.
				comprobarCuenta(idBanco, idTarjeta, cuentaTarjeta);
		
	    String sentencia = 
	    "SELECT tipo_movimiento, signo, importe, " +
	    "TO_CHAR(fecha,'dd/mm/yyyy') as fecha, " +
	    "TO_CHAR(fecha,'hh24:mi:ss') as hora "+
		"FROM movimiento "+
		"WHERE (id_banco = '"+idBanco+"')"+
		" AND (id_cbanco = "+cbanco+")";
		
		Connection conexion = Database.getInstancia().getConexion();
		
		if (conexion != null) {
			
			Statement stmt = conexion.createStatement();
	        ResultSet rs   = stmt.executeQuery(sentencia);
			Formatter fmt  = new Formatter();

	        int it = 0;
	        String resultado = "";
	        
	        while (rs.next()) {

	        	// solo obtenemos 20 movimientos como maximo
	        	it++;	            
	            if (it > 20) break;
	        	
	            fmt = new Formatter();
	        	String tipoMovimiento = rs.getString("tipo_movimiento");
	            String signo          = rs.getString("signo");
	            int    importe        = rs.getInt("importe");
	            fmt.format("%08d",importe);
	            String fecha          = rs.getString("fecha");
	            String hora           = rs.getString("hora");
	            
	            resultado += tipoMovimiento+signo+fmt.toString()+fecha+hora;
	        }       
	        // añadimos el iterador para saber cuantos movimientos van
			fmt = new Formatter();
	        fmt.format("%02d",it);

	        resultado = fmt.toString() + resultado;			
			
	        stmt.close();
	        
	        return resultado;			
		}
		
		throw new ConexionInvalidaException();
	}
	
	
	/**
	 * Elimina el importe correspondiente a una cuenta
	 */
	public static String reintegro(String idBanco, 
					               String idTarjeta, 
					               String cuentaTarjeta,
					               String importe,
					               boolean offline,
					               boolean informar) 
					               throws SQLException, 
					            	      CuentaNoValidaException, 
					            	      TarjetaNoValidaException, 
					            	      ConexionInvalidaException, 
					            	      CuentaSaldoInsuficienteException {
		
		String cbanco;
		cbanco = DBBancoOperations.
				comprobarCuenta(idBanco, idTarjeta, cuentaTarjeta);
		
		if (!offline) {
			// comprobacion importe cuenta 
			int saldo = Integer.parseInt(
					DBBancoOperations.consultaSaldo(
							idBanco, idTarjeta, cuentaTarjeta).substring(1));
			
			if (saldo < Integer.parseInt(importe))
				throw new CuentaSaldoInsuficienteException();
		}
		
	    String sentencia = 
	    "UPDATE cbanco "+
	    "SET saldo = saldo - "+importe+" "+
		"WHERE (id_banco = '"+idBanco+"')"+
		" AND (id_cbanco = "+cbanco+")";
		
		Connection conexion = Database.getInstancia().getConexion();
		
		if (conexion != null) {
			
			Statement stmt = conexion.createStatement();
			stmt.executeUpdate(sentencia);			
			stmt.close();			
			
			if (informar)
				insertarMovimiento(
						idBanco, cbanco, CodigosFAP.TM_REINTEGRO, "-", importe);
			
			return
				DBBancoOperations.consultaSaldo(
						idBanco, idTarjeta, cuentaTarjeta);
		}		
		
		throw new ConexionInvalidaException();
	}
	

	/**
	 * Añade el importe correspondiente a una cuenta
	 * @throws ConexionInvalidaException 
	 */
	public static String abono(String idBanco, 
			                String idTarjeta, 
			                String cuentaTarjeta, 
			                String importe,
			                boolean informar) 
			                throws SQLException, 
			                	   CuentaNoValidaException, 
			                	   TarjetaNoValidaException, 
			                	   ConexionInvalidaException { 
		String cbanco;
		cbanco = DBBancoOperations.
				comprobarCuenta(idBanco, idTarjeta, cuentaTarjeta);
		
		String sentencia = 
	    "UPDATE cbanco "+
	    "SET saldo = saldo + "+importe+" "+
		"WHERE (id_banco = '"+idBanco+"')"+
		" AND (id_cbanco = "+cbanco+")";
		
		Connection conexion = Database.getInstancia().getConexion();
		
		if (conexion != null) {
			
			Statement stmt = conexion.createStatement();
			stmt.executeUpdate(sentencia);			
			stmt.close();
			
			if (informar)
				insertarMovimiento(
						idBanco, cbanco, CodigosFAP.TM_ABONO , "+", importe);
			
			return 
				DBBancoOperations.consultaSaldo(idBanco, idTarjeta, cuentaTarjeta);
		}
		
		throw new ConexionInvalidaException();
	}
	
	
	/**
	 * Traspaso de saldo entre dos cuentas de una tarjeta
	 */
	public static String traspaso(String idBanco, 
			                      String idTarjeta, 
			                      String cuentaTarjetaO,
			                      String cuentaTarjetaD, 
			                      String importe,
						          boolean offline) 
			                      throws SQLException, 
			                             CuentaOrigenDestinoIgualesException,
			                             CuentaOrigenException, 
			                             CuentaDestinoException, 
			                             CuentaNoValidaException, 
			                             TarjetaNoValidaException, 
			                             CuentaOrigenSaldoInsuficienteException, 
			                             NumberFormatException, 
			                             ConexionInvalidaException, 
			                             CuentaOrigenSaldoInsuficienteException {
		
		// comprobacion cuenta origen
		String cbancoO;
		try{
			cbancoO = DBBancoOperations.
					comprobarCuenta(idBanco, idTarjeta, cuentaTarjetaO);
			
		} catch (Exception e) {
			throw new CuentaOrigenException();
		}
		
		// comprobacion cuenta destino
		String cbancoD;
		try{
			cbancoD = DBBancoOperations.
					comprobarCuenta(idBanco, idTarjeta, cuentaTarjetaD);
			
		} catch (Exception e) {
			throw new CuentaDestinoException();
		}
		
		// comprobacion cuenta origen != cuenta destino
		if (cbancoO.equals(cbancoD))
			throw new CuentaOrigenDestinoIgualesException();
		
		Connection connection = Database.getInstancia().getConexion();

		boolean commited = false;
		try {			
			connection.setAutoCommit(false);

			String origen;
			try {
				origen = DBBancoOperations.reintegro(
				   idBanco, idTarjeta, cuentaTarjetaO, importe, offline, false);
			} catch (CuentaSaldoInsuficienteException e) {
				throw new CuentaOrigenSaldoInsuficienteException();
			}
			String destino = 
				DBBancoOperations.abono(
						idBanco, idTarjeta, cuentaTarjetaD, importe, false);
		
			connection.commit();
			commited = true;
			connection.setAutoCommit(true);
			
			insertarMovimiento(
				idBanco, cbancoO, CodigosFAP.TM_TRASPASO_EMITIDO , "-", importe);
			insertarMovimiento(
				idBanco, cbancoD, CodigosFAP.TM_TRASPASO_RECIBIDO, "+", importe);
				
			return origen+destino;
		} finally {
			if (!commited) 
				connection.rollback();
		}
	}
	
	
	/**
	 * Comprueba que la tarjeta o la cuenta de la tarjeta a la que se va a 
	 * referenciar exista
	 */
	private static String comprobarCuenta(String idBanco,
			                        String idTarjeta,
			                        String cuentaTarjeta) 
			                        throws SQLException, 
			                        	   CuentaNoValidaException, 
			                               TarjetaNoValidaException, 
			                               ConexionInvalidaException {		    
		
	    String sentencia = 
	    "SELECT id_banco, id_tarjeta, id_ctarjeta, id_cbanco "+
		"FROM ctarjeta "+
		"WHERE (id_banco =   '"+idBanco+"')"+
		" AND  (id_tarjeta = '"+idTarjeta+"')"+
		" AND  (id_ctarjeta = '"+cuentaTarjeta+"')";
		
		 Connection conexion = Database.getInstancia().getConexion();
				
		if (conexion != null) {
					
			Statement stmt   = conexion.createStatement();
			ResultSet result = stmt.executeQuery(sentencia);
			String cbanco;
			       
			try {
				result.next();				
		        cbanco = result.getString("id_cbanco");         

		        // no hay cuenta de banco asociada a la cuenta de tarjeta
		        if (cbanco == null)  		        	
		        	throw new CuentaNoValidaException();
		        		        
			} // result.next() falla porque no hay datos
			catch (SQLException e) {
				throw new TarjetaNoValidaException();
			}
			
	        stmt.close();
	        
		    return cbanco;     
		}		
		
		throw new ConexionInvalidaException();
	}
	
	
	/**
	 * inserta un movimiento en la tabla de movimientos
	 */
	private static void insertarMovimiento(String idBanco, 
                                           String cuentaBanco,   
                                           String tipoMovimiento,
                                           String signo,
                                           String importe) 
                                           throws SQLException, 
                                                   ConexionInvalidaException {
		
		Connection conexion = Database.getInstancia().getConexion();

		int mov = obtenerNMovimiento(idBanco, cuentaBanco);
		
		String sentencia = 
	    "INSERT INTO movimiento "+
	    "VALUES ('"+idBanco+"',"+cuentaBanco+","+mov+","+tipoMovimiento+",'"
	    		+signo+"',"+importe+","+
	    		"(SELECT sysdate FROM dual))";
		
		
		if (conexion != null) {
			
			Statement stmt = conexion.createStatement();
			stmt.executeUpdate(sentencia);			
			stmt.close();			
		} else 
			throw new ConexionInvalidaException();
	}
	
	
	/**
	 * inserta un log en la tabla de logs
	 */
	public static void insertarLog(String idBanco,
                                   String mensaje) 
                                   throws SQLException, 
                                          ConexionInvalidaException {
		
		Connection conexion = Database.getInstancia().getConexion();

		int nlog = obtenerNLog(idBanco);
		
		String sentencia = 
	    "INSERT INTO b_log "+
	    "VALUES ('"+idBanco+"',null,"+nlog+",'"+mensaje+"',"
	    		+"(SELECT sysdate FROM dual))";
		
		if (conexion != null) {
			
			Statement stmt = conexion.createStatement();
			stmt.executeUpdate(sentencia);			
			stmt.close();			
		} else 
			throw new ConexionInvalidaException();
	}
	
	
	/**
	 * Obtiene el numero del ultimo movimiento de esa cuenta
	 */
	private static int obtenerNMovimiento(String idBanco, 
                                          String cuentaBanco) 
                                          throws SQLException {
		
		// obtenemos el número del último movimiento guardado
        String obtenerNmov = "SELECT max(id_movimiento) " +
        	                 "FROM movimiento " +
        	                 "WHERE id_banco = '"+idBanco+"' " +
        	                 "AND id_cbanco = "+cuentaBanco;
        
		Connection conexion = Database.getInstancia().getConexion();
		int mov = 0;
		
		if (conexion != null) {
			
			Statement stmt   = conexion.createStatement();
	        ResultSet result = stmt.executeQuery(obtenerNmov);	
	        // si hubo alguno antes
	        try {
		        result.next();	        
		        mov = result.getInt(1)+1;     	        
	        } catch (Exception e) {}
	        
	        stmt.close();        
		}
		
		return mov;
	}
	
	
	/**
	 * Obtiene el numero del ultimo log de esa cuenta
	 */
	private static int obtenerNLog(String idBanco) 
                                   throws SQLException {
		
		// obtenemos el número del último log guardado
        String obtenerNlog = "SELECT max(id_clog) " +
        	                 "FROM b_log " +
        	                 "WHERE id_banco = '"+idBanco+"' ";
        
		Connection conexion = Database.getInstancia().getConexion();
		int nlog = 0;
		
		if (conexion != null) {
			
			Statement stmt   = conexion.createStatement();
	        ResultSet result = stmt.executeQuery(obtenerNlog);	
	        // si hubo alguno antes
	        try {
		        result.next();	        
		        nlog = result.getInt(1)+1;     	        
	        } catch (Exception e) {}
	        
	        stmt.close();        
		}
		
		return nlog;
	}
}
